
'use strict'

const CLASS_NAME = 'BoyContainer' /** 为了防止类名被构建工具混淆掉
对象不可以依赖反射的能力获取类名, 而是应该在构造器内把类名标到类的一个字段 */

const {camel2underline, contain_seq, guid} = require('./utils')
const Wrapper = require('./Wrapper')
const ToolBox = require('./ToolBox')
const ModelDecorator = require('./ModelDecorator')
const DEFAULT_ENV = require('./env')

/** boy装饰器 */
const d_base = require('./@model/base') // boy获取基础能力
const d_crud = require('./@model/crud') // boy获取crud能力
const d_reflect = require('./@model/reflect') // boy获取反射能力
const d_registerListener = require('./@model/registerListener') // 给boy的各个生命周期开个口子, 从而使外部可以监听boy的生命周期
const d_ngAdapterWithAsync = require('./@model/ngAdapterWithAsync') // 将angular以及await/async特性适配到boy上
/** @todo 将其他特性适配到boy上 */

/** 异常 */
const PrivateConstructor = require('./exception/PrivateConstructor') // 实例是私有化的, 只能通过静态方法getInstance获取
const KeyWordCannotOverWrite = require('./exception/KeyWordCannotOverWrite') // 用户的方法为boy的关键字
const IllModel = require('./exception/ILLModel') // boy模型错误
const IllModelConfig = require('./exception/IllModelConfig') // boy模型配置错误
const UnableToPromiseUniqueBoy = require('./exception/UnableToPromiseUniqueBoy') // 给定的名字不能确定唯一的boy

/** 内置boy, 并未注册, 会在init中注册 */
const TimerBoy = require('./buildin/TimerBoy') // 定时器
const UserBoy = require('./buildin/UserBoy') // 对用户进行一些操作
const hideBoy = require('./buildin/hideBoy') // 用于调试创建失败的boy
const nextBoy = require('./buildin/nextBoy') // 用于创建分页boy
const storeBoy = require('./buildin/storeBoy') // 用于创建本地缓存

/** 将导入的装饰器注册为默认的规则 */
ModelDecorator
.addRule(d_base)
.addRule(d_crud)
.addRule(d_reflect)
.addRule(d_registerListener)
.addRule(d_ngAdapterWithAsync, ['angularjs', 'async'])


/** 使构造器私有, 从而确保单例模式 */
let instance = {
    cancreate : false,
    instance : null
}

class BoyContainer{

    static getInstance(){
        if(null === instance.instance){
            instance.cancreate = true
            instance.instance = new BoyContainer()
            instance.cancreate = false
        }
        return instance.instance
    }

    constructor(){

        if(!instance.cancreate){
            throw new PrivateConstructor()
        }

        this.__wrapper_map = {}
        this.__boy_map = {}
        this.__extra_info_map = {} /** 为了不污染boy字段, 将内置的boy的信息存储到ExtraInfo上面, boy只有一个隐藏字段__guid, 通过guid可以反查到boy的额外信息 */
        this.__waiting_commit_map = {} /** 任何boy的crud操作, 会自动开启一个事务, 当boy调用Boy.$commit方法时, 将合并操作并将结果集提交给服务器 */
        this.__test = []

        this.__keyword = ['angularjs', 'async']

        this.__env = DEFAULT_ENV
        this.__name = CLASS_NAME
        this.__model_name_current = null /** 通过构建工具传递Model名 */
        this.__init_user = [[], []]

        this.toolbox = new ToolBox()
        this.Exception = {KeyWordCannotOverWrite, IllModel, IllModelConfig, PrivateConstructor, UnableToPromiseUniqueBoy}
        
        // 启动级别
        this.INIT_0 = 0
        this.INIT_1 = 1

        this.ROOT = -2
        this.NOSEQ = -1

        /** 将以下字段暴露到鸭子接口对应的
         * 公有字段 */
        this.__exports = [
            'toolbox', 'Exception', 'INIT_0', 'INIT_1', 
            'ROOT', 'NOSEQ',
        ]
    }
    addInitListener(listener, lv = 1){
        'function' === typeof listener && [1, 1][lv] && this.__init_user[lv].push(listener)
        return this
    }
    setCurrentModelName(Model_name_current){
        /**
         * 设置即将注册的Model, 紧贴着BoyContainer.register上方使用, 这一个额外的函数通常是构造器使用
         * 不要用这个函数, 除非你明确知道他用来做什么
        */
        this.__model_name_current = Model_name_current
        return this
    }
    env(env){
        /**
         * 为boy容器添加环境变量
        */
        env = 'object' === typeof env ? env : {}
        for(let key in env){
            this.__env[key] = env[key]
        }
        return this
    }
    register(Model, Model_name){
        /**
         * 注册一个模块, 如果在异步代码中调用该函数, 两个参数都需要给出, 不然程序会报错
        */

        Model_name = Model_name || this.__model_name_current
        Wrapper.check_Model_if_ill(Model, Model_name) // 检查注册的Model是否合法

        let base_name = Model_name.split('.').at(-1)
        let wrapper = new Wrapper()
        ;/^[A-Z$]/.test(base_name) && wrapper.setLazy(false)
        wrapper.setModel(Model, Model_name)

        // 对Model进行装饰
        new ModelDecorator()
        .addHandler({Model, Model_name, wrapper, boy : this,
            env : this.__env, extra_map : this.__extra_info_map})
        .execute()

        // 将wrapper加入到wrapper_map中
        // wrapper_map格式{ name(不包括包名) : [{ name(完整的name), wrapper }] }
        this.__wrapper_map[base_name] = this.__wrapper_map[base_name] || []
        this.__wrapper_map[base_name].push({
            name : Model_name,
            wrapper : wrapper,
        })

        return wrapper
    }
    create(model_name, boy_name, config_name, extra_config, mount, point){

        
        let wrapper_list = this.getWrapper(model_name)
        if(wrapper_list.length > 1){
            // 找不到没关系, 不要出现命名冲突
            // 因为找不到不会影响逻辑, 但是命名冲突会
            // 所以, 命名冲突会报错, 但找不到不会报错
            throw new UnableToPromiseUniqueBoy(model_name)
        }
        let wrapper = wrapper_list[0]
        if(undefined === wrapper){
            wrapper = this.getWrapper('hideBoy')[0]
            extra_config = {model_name}
        }

        let Model = wrapper.getModel()
        let config = wrapper.getConfig(config_name, extra_config)
        let boy = new Model()._init(mount, point)

        for(let [k, v] of Object.entries(config)){
            let arr = []
            if(v instanceof Array){
                v.forEach(elem=>{
                    if('object' === typeof elem){
                        elem.mount = boy
                        elem.point = k
                        arr.push(this.createByJson(elem))
                    }else{
                        arr.push(this.createByCmd(elem, boy, k))
                    }
                })
                boy[k] = arr            
            }else if('object' === typeof v){
                v.mount = boy
                v.point = k
                boy[k] = this.createByJson(v)
            }else{
                boy[k] = this.createByCmd(v, boy, k)
            }
        }

        boy._setName(boy_name)
        boy._setMount(mount)
        boy._setPoint(point)

        let base_name = boy_name.split('.').at(-1)

        this.__boy_map[base_name] = this.__boy_map[base_name] || {}
        this.__boy_map[base_name][boy.$guid()] = {
            uuid : boy.$guid(),
            name : boy_name,
            boy : boy
        }

        return boy
    }
    createByCmd(cmd, mount, point){
        
        if('string' !== typeof cmd) return cmd
        if(!/^\@+\+/.test(cmd)) return cmd
        if('@' === cmd[1]) return cmd.substring(1)

        cmd = cmd.replace(/^\@\+/, '')
        let [model_name, boy_name, config_name] = cmd.split(':')

        let model_name_w = this.getWrapper(model_name)[0] ? this.getWrapper(model_name)[0].getModelName() : 'noname'

        boy_name = boy_name || camel2underline(model_name_w)
        config_name = config_name || 'default'
        return this.create(model_name, boy_name, config_name, {}, mount, point)
    }
    createByJson(json){

        let {name, boy, config, extra, mount, point} = json
        boy = boy || 'hideBoy'

        let model_name_w = this.getWrapper(boy)[0] ? this.getWrapper(boy)[0].getModelName() : 'noname'
        name = name || camel2underline(model_name_w)
        config = config || 'default'
        extra = extra || {}

        return this.create(boy, name, config, extra, mount, point)
    }
    get(name){
        let name_arr = name.split('.')
        let boy_arr = this.__boy_map[name_arr.at(-1)] || {}
        let res = []
        Object.values(boy_arr).forEach(elem=>{
            contain_seq(elem.name.split('.'), name_arr) &&
                res.push(elem.boy)
        })
        return res
    }
    // 遍历所有boy
    each(callback){
    }
    init(){

        // 运行init_0级别用户脚本
        this.__init_user[this.INIT_0].forEach(init => init())
        // 挂载默认对象
        TimerBoy(this)
        UserBoy(this)
        hideBoy(this)
        for(let wrapper of Object.values(this.__wrapper_map)){
            if(!wrapper.isLazy()){
                wrapper.eachConfig(config => {
                    if(!config.isLazy()){

                    }
                })
            }
        }
        // 运行init_1级别的用户脚本
        this.__init_user[this.INIT_1].forEach(init => init())
        return this
    }
    getWrapper(Model_name){
        /**
         * 根据模块名获取wrapper
        */
        let Model_name_s = Model_name.split('.')
        let base_name = Model_name_s.at(-1)
        let res = []

        let map = this.__wrapper_map[base_name] || []

        map.forEach(elem => {
            let arr = elem.name.split('.') || []
            contain_seq(arr, Model_name_s) && res.push(elem.wrapper)
        })
        return res
    }
    test(){
    }

}



module.exports = BoyContainer